import InsUtils from "./ins-utils";
import BattleUtils from "./battle-utils";
import SocketUtils from "./socket-utils";
import {
    APP,
    BATTLE,
    GAME_OVER_TYPE,
    INS_KEY,
    PAGE_STATUS,
    ROOM_JOIN_TYPE,
    ROOM_STATUS,
    USER_STATUS
} from "../configs/config";
import CryptorUtils from "./cryptor-utils";
import UserDao from "../dao/user-dao";
import BattleDao from "../dao/battle-dao";
import BattleHistoryDao from "../dao/battle-history-dao";
import BattleUserDao from "../dao/battle-user-dao";
import UserStateDao from "../dao/user-state-dao";
import RoomFlowDao from "../dao/room-flow-dao";

const insUtils = new InsUtils();
const battleUtils = new BattleUtils();
const socketUtils = new SocketUtils();

class RoomUtils {

    /**
     * 为用户分配房间（用户类型）
     * @param userId
     * @param userType
     * @param historyRoomIds
     * @param appointRoomId
     */
    distributionRoom = async (userId: string, userType: string, historyRoomIds: string[], appointRoomId?: string) => {
        const log = global.logUtils.createContext('RoomUtils', 'distributionRoom', {userId, roomId: appointRoomId});
        if (appointRoomId) {
            // 记录房间号
            await UserStateDao.updateUserStateTable([['room_id', appointRoomId]], [['user_id', userId]]);
            log.info(`用户[${userId}]分配的房间号为[${appointRoomId}](指定加入)`);
            return appointRoomId;
        }

        log.info(`用户[${userId}]请求分配的房间，历史分配过的有：`, historyRoomIds);
        // 把用户被踢的那些房间号计入不能匹配列表
        const roomIds: string[] = await RoomFlowDao.getKickRoomIdsBy(userId, APP.KICK_LIMIT_SECONDS);
        historyRoomIds.push(...roomIds);
        log.info(`用户[${userId}]被踢的房间列表有：`, roomIds);

        // 优先从数据库查询数据进行分配
        const rows: any = await UserStateDao.queryValidRooms(userId, historyRoomIds, ROOM_STATUS.WAIT);
        if (rows.length === 0) {
            log.info(`从数据库中未检索到可分配的房间, 开始自由分配`);
            const tryRoomIds = [];
            // 录入已经分配过的房间
            tryRoomIds.push(...historyRoomIds);

            for (let i = 0; i < BATTLE.MAX_ROOM; ++i) {
                const roomId = Math.floor(Math.random() * BATTLE.MAX_ROOM) + 1;
                if (!tryRoomIds.some(roomIdItem => roomIdItem === roomId)) {
                    tryRoomIds.push(roomId);
                    // 判断数据库中是否有该房间且房间的状态是否可加入
                    let canJoinRoomFlag = false;
                    const rows: any = await UserStateDao.queryUserStateTable([['room_id', roomId]]);
                    if (rows.length === 0) {
                        canJoinRoomFlag = true;
                    } else if ((rows[0].roomStatus === ROOM_STATUS.WAIT)) {
                        canJoinRoomFlag = true;

                        // 正式用户和游客分开匹配
                        // const userId = rows[0].userId;
                        // const user: any = await UserDao.getUserByPrimaryKey(userId);
                        // if(user.userType === userType) {
                        //     canJoinRoomFlag = true;
                        // }
                    }
                    // 判断是否可以进房间
                    if (canJoinRoomFlag) {
                        // 记录房间号
                        await UserStateDao.updateUserStateTable([['room_id', roomId]], [['user_id', userId]]);
                        log.info(`为用户[${userId}]分配的房间号为[${roomId}]`);
                        return roomId;
                    }
                }
            }
            log.info(`已遍历完所有可分配的房间，无法为其[${userId}]进行分配(配额不够)，配额为: ${BATTLE.MAX_ROOM}`);
            return null;
        } else {
            // 随机分配一间
            const userState = rows[Math.floor(Math.random() * rows.length)];
            await UserStateDao.updateUserStateTable([['room_id', userState.roomId]], [['user_id', userId]]);
            log.info(`为用户[${userId}]分配的房间号为[${userState.roomId}](DB)`);
            return userState.roomId;
        }
    }


    /**
     * 离开房间处理
     * @param userId
     * @param roomId
     * @param notifyEnemy 是否需要通知房间内的对手(有些操作单独通知或被重复通知)
     * @returns {Promise<void>}
     */
    leaveRoom = async (userId: string, roomId: string, notifyEnemy: boolean = true) => {
        const log = global.logUtils.createContext('RoomUtils', 'leaveRoom', {userId, roomId});

        if (!roomId) {
            log.info(`[${userId}]所在的房间号为空`);
            return;
        }
        // 检查用户是否在房间中
        const userStateRows: any = await UserStateDao.queryUserStateTable([['room_id', roomId]]);
        if (userStateRows.length === 0 || !userStateRows.some((user: any) => user.userId === userId)) {
            log.info(`用户[${userId}]早已离开房间[${roomId}]`);
        } else {
            const columns = [];
            columns.push(['room_status', null]);
            columns.push(['is_ready', false]);
            columns.push(['room_id', null]);
            columns.push(['battle_id', null]);
            columns.push(['user_status', null]);
            columns.push(['user_page', PAGE_STATUS.PLATFORM]);
            await UserStateDao.updateUserStateTable(columns, [['user_id', userId]]);
            const userState = userStateRows.find((user: any) => user.userId === userId);
            log.info(`[${userId}]在房间的游离数据为：`, userState);

            // 离开房间时进行数据累计更新
            const io = insUtils.getIns(INS_KEY.IO_KEY);
            if (userState.userStatus === USER_STATUS.WATCH) {
                const user: any = UserDao.getUserByPrimaryKey(userId);
                // 发送离开观战通知
                io.to(roomId).emit('watchNotifyRespApi', CryptorUtils.encrypt({
                    code: 'success',
                    msg: `[${userId}]离开观战`,
                    data: {
                        action: 'sub',
                        // action: add 和 sub 时 watchUserId 必需有值
                        watchUserId: userId,
                        watchUserName: user.userName,
                        // action: update 时 watchUserList 为全量数据
                        watchUserList: [],
                    }
                }));
                log.info(`[${userId}]操作离开房间[${roomId}]，其用户状态为[${userState.userStatus}]，不需要更新房间状态`);
            } else {
                // 在房间或对战时，判断是否有对手，若有则更新对手所在房间的状态
                const enemy = userStateRows.find((user: any) => user.userId !== userId && user.userStatus !== USER_STATUS.WATCH);
                if (enemy) {
                    // 更新房间状态
                    const updColumns = [];
                    updColumns.push(['room_status', ROOM_STATUS.WAIT]);
                    updColumns.push(['first', true]);
                    updColumns.push(['is_room_admin', true]);
                    await UserStateDao.updateUserStateTable(updColumns, [['user_id', enemy.userId]]);
                    log.info(`[${enemy.userId}]所在房间[${roomId}]的状态已更新为[${ROOM_STATUS.WAIT}]`);
                }
                // 1.如果需要通知对手，则直接发送全局事件，或没有对手，也可以直接发送全局事件
                if (notifyEnemy || !enemy) {
                    // 对房间内的用户进行已经离开房间的提醒(离开用户的对手以及吃瓜群众)
                    const sockets = socketUtils.getSocketsBy(userState.token);
                    for (let socket of sockets) {
                        // 发送给除对手以外的所有人
                        socket.to(roomId).emit('enemyLeaveRoomRespApi', CryptorUtils.encrypt({
                            code: 'success',
                            msg: '对方离开房间',
                            data: {
                                userId: userId,
                            }
                        }));
                    }
                    log.info(`我方[${userId}]离开房间[${roomId}]时，已通知所有人(不包括自己)`);
                } else {
                    // 2.如果不需要通知对手的情况下，直接拿到对手的socket，然后发给除它以外的所有人
                    const sockets = socketUtils.getSocketsBy(enemy.token);
                    for (let socket of sockets) {
                        // 发送给除对手以外的所有人
                        socket.to(roomId).emit('enemyLeaveRoomRespApi', CryptorUtils.encrypt({
                            code: 'success',
                            msg: '对方离开房间',
                            data: {
                                userId: userId,
                            }
                        }));
                    }
                    log.info(`我方[${userId}]离开房间[${roomId}]时，已通知对手外的所有人`);
                }
            }
            io.emit('onlineCountRespApi', CryptorUtils.encrypt({
                code: 'success',
                msg: '在线数据累计',
                data: {
                    action: 'add',
                    onlineCount: -1,
                    battleCount: 0,
                },
            }));
            // 发送全局信息
            io.emit('leaveRoomRespApi', CryptorUtils.encrypt({
                code: 'success',
                msg: '全局-用户离开房间',
                data: {
                    roomId: roomId,
                    userId: userId,
                },
            }));
        }
        log.info(`[${userId}]离开了[${roomId}]号房间`);
    }

    /**
     * 处理游戏胜利
     * @param battleId
     * @param userId
     * @param type
     */
    handleGameResult = async (battleId: string, userId: string, type: string) => {
        const log = global.logUtils.createContext('RoomUtils', 'handleGameResult', {userId, battleId});

        const io = insUtils.getIns(INS_KEY.IO_KEY);
        log.info(`对战房间[${battleId}]请求结算, [${userId}]的结算类型为:[${type}]`);
        // 获取房间信息
        const battleRoom: any = await BattleDao.queryBattleTable([['id', battleId]]);
        log.info(`对战房间[${battleId}]对应的数据为:`, battleRoom);
        if (battleRoom.length === 0) {
            log.info(`对战房间[${battleId}]无法被获取，结算中止(未结算)`);
        } else {
            const [battle] = battleRoom;
            const {roomStatus} = battle;
            if (roomStatus === ROOM_STATUS.BATTLE_OVER) {
                log.info(`对战房间[${battleId}]已经被结算，本次结算中止`);
                return false;
            } else if (roomStatus !== ROOM_STATUS.BATTLE && roomStatus !== ROOM_STATUS.TIMEOUT) {
                log.info(`对战房间[${battleId}]状态不为对局，结算中止(未结算) ${roomStatus}`);
                return false;
            }
            // 清除游戏倒计时
            insUtils.getIns(INS_KEY.JOB_KEY).deleteAllBattleData(battleId);

            // 获取对战房间的用户信息
            const battleUserData: any = await BattleUserDao.queryBattleUserTable([['battle_id', battleId]]);
            const battleUser = battleUserData.find((user: any) => user.userId === userId);
            const userDetail: any = await UserDao.getUserByPrimaryKey(battleUser.userId);
            const battleEnemy = battleUserData.find((user: any) => user.userId !== userId);
            const enemyDetail: any = await UserDao.getUserByPrimaryKey(battleEnemy.userId);
            const userState: any = await UserStateDao.getUserStateByUserId(battleUser.userId);
            const enemyState: any = await UserStateDao.getUserStateByUserId(battleEnemy.userId);

            log.info(`开始结算[${battleUser.userId}]PK[${battleEnemy.userId}]`);

            // 先将房间状态进行更新(防止重复结算)
            await BattleDao.updateBattleStatus(battleId, ROOM_STATUS.BATTLE_OVER);

            // 获取棋盘的对局信息
            const userHistoryData = await BattleHistoryDao.getLastBattleMapHistory(battleId, userId);
            const gameMap = JSON.parse(userHistoryData.gameMap);
            log.info(`[${userId}]最后一条历史数据为:`, userHistoryData);

            let isRedColorWin = null;
            if (type === GAME_OVER_TYPE.USER_TIMEOUT) {
                // 超时方的对手胜利
                isRedColorWin = !battleUser.first;
            } else if (type === GAME_OVER_TYPE.BATTLE) {
                // 对局结束，当前的落子方的对手胜利
                isRedColorWin = !userHistoryData.isRedMove;
            } else if (type === GAME_OVER_TYPE.USER_PEACE) {
                // 对方发起求和，我方同意时会进行结算，优势方为胜利方
                isRedColorWin = battleUtils.calcBestUser(gameMap);
            } else if (type === GAME_OVER_TYPE.ADMIT_DEFEAT) {
                // 我方认输，对手方胜利
                isRedColorWin = !battleUser.first;
            } else if (type === GAME_OVER_TYPE.USER_LEAVE) {
                // 我方逃跑，对手胜利
                isRedColorWin = !battleUser.first;
            }

            const winUserId = type === GAME_OVER_TYPE.USER_PEACE ? null : battleUser.first === isRedColorWin ? battleUser.userId : battleEnemy.userId;
            const winMsg = `${type === GAME_OVER_TYPE.USER_PEACE ? '和棋' : isRedColorWin ? '黑棋' : '白棋'}`;
            const winMessage = `房间[${battleUser.roomId}], 胜利方：${winUserId || ''}(${winMsg})`;
            log.info(winMessage);
            // 更新对战结果
            await BattleDao.updateBattleResult(battleId, type, winMsg, winUserId);

            // 对棋盘进行结算
            const winResult = battleUtils.calcScore(gameMap, type, userHistoryData.stepCount);
            log.debug("得分结果：", winResult);

            // 保存结算的数据
            const userData = {
                score: userDetail.score + (battleUser.first === isRedColorWin ? winResult.winScore : winResult.failScore),
                pkTotalCount: userDetail.pkTotalCount + (winResult.isValidScore ? 1 : 0),
                pkWinCount: userDetail.pkWinCount + ((battleUser.first === isRedColorWin && winResult.isValidScore) ? 1 : 0),
                pkFailCount: userDetail.pkFailCount + ((battleUser.first !== isRedColorWin && winResult.isValidScore) ? 1 : 0),
                pkPeaceCount: userDetail.pkPeaceCount + ((type === GAME_OVER_TYPE.USER_PEACE && winResult.isValidScore) ? 1 : 0),
                // 离线率暂时设置成0
                pkOfflineCount: 0,
                userId: battleUser.userId,
            };
            // 更新数据库的对战信息
            await BattleUserDao.updateBattleUserResult(battleId, userData.userId, battleUser.first === isRedColorWin ? winResult.winScore : winResult.failScore);

            // 更新数据库的信息
            await UserDao.updateUser(userData.userId, userData.score, userData.pkTotalCount, userData.pkWinCount, userData.pkOfflineCount, userData.pkFailCount, userData.pkPeaceCount);

            // 对手方
            const enemyData = {
                score: enemyDetail.score + (battleEnemy.first === isRedColorWin ? winResult.winScore : winResult.failScore),
                pkTotalCount: enemyDetail.pkTotalCount + (winResult.isValidScore ? 1 : 0),
                pkWinCount: enemyDetail.pkWinCount + ((battleEnemy.first === isRedColorWin && winResult.isValidScore) ? 1 : 0),
                pkFailCount: enemyDetail.pkFailCount + ((battleEnemy.first !== isRedColorWin && winResult.isValidScore) ? 1 : 0),
                pkPeaceCount: enemyDetail.pkPeaceCount + ((type === GAME_OVER_TYPE.USER_PEACE && winResult.isValidScore) ? 1 : 0),
                pkOfflineCount: 0,
                userId: battleEnemy.userId,
            };
            // 更新数据库的对战信息
            await BattleUserDao.updateBattleUserResult(battleId, enemyData.userId, battleEnemy.first === isRedColorWin ? winResult.winScore : winResult.failScore);

            // 更新数据库的信息
            await UserDao.updateUser(enemyData.userId, enemyData.score, enemyData.pkTotalCount, enemyData.pkWinCount, enemyData.pkOfflineCount, enemyData.pkFailCount, enemyData.pkPeaceCount);

            // 更新双方用户的游离状态
            const updUserColumn = [];
            updUserColumn.push(['is_ready', false]);
            updUserColumn.push(['user_page', userState.joinType === ROOM_JOIN_TYPE.RANDOM ? PAGE_STATUS.PLAYER_RANDOM : PAGE_STATUS.PLAYER_FREEDOM]);
            updUserColumn.push(['first', winResult.isValidScore ? !battleUser.first : battleUser.first]);
            updUserColumn.push(['room_status', ROOM_STATUS.MULTIPLE_WAIT]);
            updUserColumn.push(['user_status', null]);
            updUserColumn.push(['battle_Id', null]);
            await UserStateDao.updateUserStateTable(updUserColumn, [['user_id', battleUser.userId]]);

            const updEnemyColumn = [];
            updEnemyColumn.push(['is_ready', false]);
            updEnemyColumn.push(['user_page', enemyState.joinType === ROOM_JOIN_TYPE.RANDOM ? PAGE_STATUS.PLAYER_RANDOM : PAGE_STATUS.PLAYER_FREEDOM]);
            updEnemyColumn.push(['first', winResult.isValidScore ? !battleEnemy.first : battleEnemy.first]);
            updEnemyColumn.push(['room_status', ROOM_STATUS.MULTIPLE_WAIT]);
            updEnemyColumn.push(['user_status', null]);
            updEnemyColumn.push(['battle_Id', null]);
            await UserStateDao.updateUserStateTable(updEnemyColumn, [['user_id', battleEnemy.userId]]);

            // 结算信息发送给双方
            io.to(battleUser.roomId).emit('gameWinRespApi', CryptorUtils.encrypt({
                code: 'success',
                msg: '对局结算完成',
                data: {
                    // 相关扣分数据
                    winScore: winResult.winScore,
                    failScore: winResult.failScore,
                    // 是否为红棋胜
                    isRedColorWin: isRedColorWin,
                    // 游戏结算类型
                    type: type,
                    // 对局步数
                    stepCount: userHistoryData.stepCount,
                },
            }));
            io.emit('onlineCountRespApi', CryptorUtils.encrypt({
                code: 'success',
                msg: '在线数据累计',
                data: {
                    action: 'add',
                    // 在线人数不变
                    onlineCount: 0,
                    // 减少一场对局
                    battleCount: -1,
                },
            }));

            io.emit('roomStatusChangeRespApi', CryptorUtils.encrypt({
                code: 'success',
                msg: '全局-房间状态改变通知(对局结算)',
                data: {
                    roomId: battleUser.roomId,
                },
            }));

            // 清除缓存
            await battleUtils.clearBattleCache(battleId, userState.userId);
            await battleUtils.clearBattleCache(battleId, enemyState.userId);
            log.info(`房间:[${battleUser.roomId}]结算完成`);
        }
    }

}

export default RoomUtils;